package at.ttasat;

import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

import at.tasat.util.Version;

public class SolverTestResult implements Serializable {
	private static final long serialVersionUID = -2466756901344932064L;
	private final String computerName;
	private final Date date;
	private final String version;
	private final TasatType tasatType;
	private final String parameters;
	private final int runs;
	private final long startSeed;
	private final RunResult[] results;
	private final int maximalVariables;
	private final int maximalClauses;
	private final double weightingFactor;

	public SolverTestResult(TasatType tasatType, String[] parameters, int runs, long startSeed, RunResult[] results, int maximalVariables, int maximalClauses, double weightingFactor) throws IOException {
		this.computerName = getLocalComputerName();
		this.date = new Date();
		this.version = Version.getVersion();
		this.tasatType = tasatType;
		this.parameters = Util.combineParameters(parameters);
		this.runs = runs;
		this.startSeed = startSeed;
		this.results = results;
		this.maximalVariables = maximalVariables;
		this.maximalClauses = maximalClauses;
		this.weightingFactor = weightingFactor;
	}

	private static String getLocalComputerName() throws UnknownHostException {
		return InetAddress.getLocalHost().getHostName();
	}

	public boolean isSameComputer() throws UnknownHostException {
		return computerName.equals(getLocalComputerName());
	}

	public void printResults() {
		DateFormat format = new SimpleDateFormat("dd.MM.YYYY - HH:mm:ss");
		System.out.println("Run on: " + computerName + " " + format.format(date));
		switch (tasatType) {
		case Tasat:
			System.out.println("Normal tasat");
			break;
		case P_Tasat:
			System.out.println("Parallel tasat");
			break;
		case L_Tasat:
			System.out.println("Learning tasat");
			break;
		}
		System.out.println("Version: " + version);
		System.out.println("Parameters: " + parameters);
		System.out.println("Maximal Variables: " + maximalVariables);
		System.out.println("Maximal Clauses: " + maximalClauses);
		System.out.println("Weighting Factor: " + weightingFactor);
		int satisfiable = 0;
		for (RunResult result : results) {
			if (result.getResult()) {
				satisfiable++;
			}
		}
		double satisfiableRate = 100.0 * (double) satisfiable / (double) results.length;
		System.out.println("Satisfiable instances: " + satisfiableRate + "%");
		long time = getWholeTestTime();
		System.out.println("Total Time: " + time + " ms");
		System.out.println("Average Run Time: " + time / runs + " ms");
	}

	public void printComparison(SolverTestResult compareTo) {
		if (runs != compareTo.runs || startSeed != compareTo.startSeed || maximalVariables != compareTo.maximalVariables || maximalClauses != compareTo.maximalClauses) {
			System.out.println("Test cannot be compared");
			throw new IllegalArgumentException("compareTo");
		}
		if (!computerName.equals(compareTo.computerName)) {
			System.out.println("Test was executed on an other computer");
			throw new IllegalArgumentException("compareTo");
		}
		Random random = new Random(startSeed);
		Averager averageSpeedup = new Averager();
		Averager satSpeedup = new Averager();
		Averager unsatSpeedup = new Averager();

		Averager satTimeBase = new Averager();
		Averager unsatTimeBase = new Averager();
		Averager satTimeComparison = new Averager();
		Averager unsatTimeComparison = new Averager();

		for (int i = 0; i < runs; i++) {
			long seed = random.nextLong();
			RunDescription runDescription = new RunDescription(seed, maximalVariables, maximalClauses, weightingFactor);
			double speedup = getSpeedup(results[i].getTime(), compareTo.results[i].getTime());
			averageSpeedup.addValue(speedup);
			System.out.println("Run Seed=" + seed + " Variables=" + runDescription.getVariables() + " Clauses=" + runDescription.getClauses() + (results[i].getResult() ? " Sat" : " Unsat")
					+ " BaseTime=" + results[i].getTime() + " ms ComparisonTime=" + compareTo.results[i].getTime() + " ms Speedup=" + speedup + "%");
			if (results[i].getResult() != compareTo.results[i].getResult()) {
				System.out.println("Result of run with seed " + seed + " was different");
			}
			if (results[i].getResult()) {
				satSpeedup.addValue(speedup);
				satTimeBase.addValue(results[i].getTime());
				satTimeComparison.addValue(compareTo.results[i].getTime());
			} else {
				unsatSpeedup.addValue(speedup);
				unsatTimeBase.addValue(results[i].getTime());
				unsatTimeComparison.addValue(compareTo.results[i].getTime());
			}
		}
		System.out.println();
		System.out.println("SAT:");
		printStatistics(satSpeedup, satTimeBase, satTimeComparison);
		System.out.println();
		System.out.println("UNSAT:");
		printStatistics(unsatSpeedup, unsatTimeBase, unsatTimeComparison);
		System.out.println();
		System.out.println("Average Speedup: " + averageSpeedup.getAverage() + "%");
		long wholeTestTime = getWholeTestTime();
		long wholeTestTimeComparison = compareTo.getWholeTestTime();
		double wholeSpeedup = getSpeedup(wholeTestTime, wholeTestTimeComparison);
		System.out.println("Total Speedup: " + wholeSpeedup + "%");
		double wholeSpeedupTimes = (double) wholeTestTime / (double) wholeTestTimeComparison;
		System.out.println("Total Speedup: " + wholeSpeedupTimes + " Times");
	}

	private void printStatistics(Averager speedup, Averager timeBase, Averager timeComparison) {
		System.out.println("Average Time: Base=" + (long) timeBase.getAverage() + " ms Comparison=" + (long) timeComparison.getAverage() + " ms");
		System.out.println("Average Speedup: " + speedup.getAverage() + "%");
		System.out.println("Total Time: Base=" + (long) timeBase.getSum() + " ms Comparison=" + (long) timeComparison.getSum() + " ms");
		System.out.println("Total Speedup: " + getSpeedup((long) timeBase.getSum(), (long) timeComparison.getSum()) + "%");
		System.out.println("Total Speedup: " + timeBase.getSum() / timeComparison.getSum() + " Times");
	}

	private double getSpeedup(long baseTime, long compareToTime) {
		return 100.0 * ((double) (baseTime - compareToTime) / (double) Math.min(baseTime, compareToTime));
	}

	public long getWholeTestTime() {
		long time = 0;
		for (RunResult result : results) {
			time += result.getTime();
		}
		return time;
	}

	public String getComputerName() {
		return computerName;
	}

	public Date getDate() {
		return date;
	}

	public int getRuns() {
		return runs;
	}

	public long getStartSeed() {
		return startSeed;
	}

	public int getMaximalVariables() {
		return maximalVariables;
	}

	public int getMaximalClauses() {
		return maximalClauses;
	}

	public double getWeightingFactor() {
		return weightingFactor;
	}

	public TasatType getTasatType() {
		return tasatType;
	}

	public String getParameters() {
		return parameters;
	}
}
